using System;
using System.Collections.Generic;
using System.Net;
using System.Web;
using System.Xml;
using System.Xml.Serialization;

using Impact.Controllers;
using Impact.Infrastructure;
using Impact.Infrastructure.Logging;
using Impact.Model.Objects;
using Impact.Model.Service;

using StructureMap;

namespace Impact.Web.Foundation.Handlers
{
    /// <summary>
    /// Handler for Google Sitemap functionality.  When Google requests the sitemap from this
    /// website, it returns a new list of all pages, articles, tags, etc. on the site.  This
    /// class can also be set up to periodically submit changes to Google (and others).
    /// </summary>
    public class Sitemap : IHttpHandler
    {

        #region IHttpHandler Members

        bool IHttpHandler.IsReusable
        {
            get { return true; }
        }

        void IHttpHandler.ProcessRequest( HttpContext context )
        {

            UrlSet urlSet = GenerateSitemap();
            XmlSerializer xSer = new XmlSerializer( urlSet.GetType() );

            context.Response.ContentType = "text/xml";
            xSer.Serialize( context.Response.OutputStream, urlSet );

        }

        #endregion

        /// <summary>
        /// Retrieves all publicly accessible URLs and creates an URL Set object
        /// </summary>
        public UrlSet GenerateSitemap()
        {
            UrlSet set = new UrlSet();

            IPageService pageService = ObjectFactory.GetInstance<IPageService>();
            ImpactPageCollection pages = pageService.FetchAllPages( true );
            foreach ( ImpactPage page in pages )
            {
                Url url = new Url();
                url.Loc = WebUtility.ToAbsolute( ContentPresenter.FormatUrl( page ) ).ToString();
                url.LastMod = page.ModifiedOn ?? page.CreatedOn;
                set.Add( url );
            }


            ITagService tagService = ObjectFactory.GetInstance<ITagService>();
            TagCollection tags = tagService.FetchAll();
            foreach ( Tag tag in tags )
            {
                Url url = new Url();
                url.Loc = WebUtility.ToAbsolute( ContentPresenter.FormatUrl( tag ) ).ToString();
                url.LastMod = tag.CreatedOn;
                set.Add( url );
            }

            ICategoryService categoryService = ObjectFactory.GetInstance<ICategoryService>();
            ArticleCategoryCollection categories = categoryService.FetchAll( true );
            IArticleService articleService = ObjectFactory.GetInstance<IArticleService>();
            foreach ( ArticleCategory category in categories )
            {
                Url url = new Url();
                url.Loc = WebUtility.ToAbsolute( ContentPresenter.FormatUrl( category ) ).ToString();
                url.LastMod = (DateTime)( category.ModifiedOn ?? category.CreatedOn );
                set.Add( url );

                ArticleCollection articles = articleService.FetchByCategory( category.CategoryId, true );
                foreach ( Article article in articles )
                {
                    Url sub = new Url();
                    sub.Loc = WebUtility.ToAbsolute( ContentPresenter.FormatUrl( article ) ).ToString();
                    sub.LastMod = article.ModifiedOn ?? article.CreatedOn;
                    set.Add( sub );
                }
            }

            // TODO: add .aspx pages?

            return set;
        }


        /// <summary>
        /// returns the dateTime for the most recently modified Page, Article, Category or Tag
        /// </summary>
        private static DateTime GetLastModified()
        {

            DateTime article = (DateTime)Article.CreateQuery().GetMax( Article.Columns.ModifiedOn );
            DateTime category = (DateTime)ArticleCategory.CreateQuery().GetMax( ArticleCategory.Columns.ModifiedOn );
            DateTime page = (DateTime)ImpactPage.CreateQuery().GetMax( ImpactPage.Columns.ModifiedOn );
            DateTime tag = (DateTime)Tag.CreateQuery().GetMax( Tag.Columns.CreatedOn );

            DateTime output = article;

            if ( category.CompareTo( output ) > 0 )
                output = category;

            if ( page.CompareTo( output ) > 0 )
                output = page;

            if ( tag.CompareTo( output ) > 0 )
                output = tag;

            return output;

        }

        internal static void SendUpdates( HttpContext context )
        {
            if ( GetLastModified() > lastUpdated )
            {

                Uri path = new Uri( string.Format( "{0}://{1}{2}", context.Request.Url.Scheme,
                    context.Request.Url.Authority, VirtualPathUtility.ToAbsolute( "~/!sitemap.ashx" ) ) );
                string googleSitemapUrl = string.Format( "http://www.google.com/webmasters/tools/ping?sitemap={0}",
                    HttpUtility.UrlEncode( path.ToString() ) );
                
                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create( googleSitemapUrl );
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();

                string logMessage = "Sitemap update attempt";

                if ( response.StatusCode == HttpStatusCode.OK )
                {
                    logMessage = "Sitemap update submitted to Google.";
                }
                else
                {
                    logMessage = string.Format( "Sitemap update attempt to Google failed: {0}", 
                        response.StatusDescription );
                }

                ILogger logger = ObjectFactory.GetInstance<ILogger>();
                logger.Write( logMessage, System.Diagnostics.TraceEventType.Information );

                lastUpdated = DateTime.Now;
            }
        }

        private static DateTime lastUpdated = DateTime.MinValue;


    }

    /// <summary>
    /// Serializable class that mimics the schema for a Google sitemap
    /// </summary>
    [Serializable]
    [XmlType( "urlset", Namespace = "http://www.google.com/schemas/sitemap/0.84" )]
    [XmlRoot( "urlset", Namespace = "http://www.google.com/schemas/sitemap/0.84" )]
    public class UrlSet : List<Url>
    {

    }

    /// <summary>
    /// Singular URL entry in a Google sitemap
    /// </summary>
    [Serializable]
    [XmlType( "url" )]
    [XmlRoot( "url" )]
    public class Url
    {

        /// <summary>
        /// location of the page
        /// </summary>
        [XmlElement( "loc" )]
        public string Loc
        {
            get { return loc; }
            set { loc = value; }
        }
        private string loc;

        /// <summary>
        /// last modified date of the page
        /// </summary>
        [XmlElement( "lastmod" )]
        public DateTime LastMod
        {
            get { return lastMod; }
            set { lastMod = value; }
        }
        private DateTime lastMod;

        /// <summary>
        /// frequency of change for the page
        /// </summary>
        [XmlElement( "changefreq" )]
        public string ChangeFreq
        {
            get { return changeFreq; }
            set { changeFreq = value; }
        }
        private string changeFreq;

        /// <summary>
        /// Gets and sets the priority of the page. Must be between 0.0 and 1.0.
        /// </summary>
        [XmlElement( "priority" )]
        public float Priority
        {
            get { return priority; }
            set
            {
                if ( value >= 0.0 || value <= 1.0 )
                {
                    priority = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException( "Must be between 0.0 and 1.0" );
                }
            }
        }
        private float priority = 1.0F;



    }
}
